package com.stox.shared.model.enumeration;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import com.stox.shared.i18n.MessageSource;
import com.stox.shared.model.Bar;

public enum BarSpan {
	/*
	 * TICK(Calendar.SECOND, 5), MINUTELY_1(Calendar.MINUTE, 1), MINUTELY_5(Calendar.MINUTE, 5),
	 * MINURELY_15(Calendar.MINUTE, 15), HALF_HOURLY(Calendar.MINUTE, 30), HOURLY(Calendar.HOUR, 1),
	 */
	DAILY("DAILY", Calendar.DATE, Calendar.MONTH, 1),
	WEEKLY("WEEKLY", Calendar.WEEK_OF_YEAR, Calendar.MONTH, 1),
	MONTHLY("MONTHLY", Calendar.MONTH, Calendar.YEAR, 1);

	private final int unit;
	private final int higherUnit;
	private final int count;
	private final String key;

	private BarSpan(final String key, final int unit, final int higherUnit, final int count) {
		this.unit = unit;
		this.count = count;
		this.higherUnit = higherUnit;
		this.key = key;
	}

	public int getHigherUnit() {
		return higherUnit;
	}

	public long getTimeInMillis() {
		final Calendar calendar = Calendar.getInstance();
		final long barTime = calendar.getTimeInMillis();
		calendar.add(unit, count);
		return calendar.getTimeInMillis() - barTime;
	}

	/**
	 * Cached instance to improve performance
	 */
	private static Calendar calendar = Calendar.getInstance();

	public Date add(final Date date, final int count) {
		synchronized (calendar) {
			calendar.setTime(date);
			add(calendar, count);
			return calendar.getTime();
		}
	}

	public void add(final Calendar calendar, final int count) {
		calendar.add(unit, count * this.count);
	}

	public void merge(final List<Bar> higherBars, final Bar bar) {

		int value = -1;
		Bar higherBar = null;
		final Calendar calendar = Calendar.getInstance();

		if (!higherBars.isEmpty()) {
			higherBar = higherBars.get(higherBars.size() - 1);
			calendar.setTime(higherBar.getDate());
			value = calendar.get(getUnit());
		}

		calendar.setTime(bar.getDate());
		if (value != calendar.get(getUnit())) {
			higherBar = new Bar();
			higherBar.setOpen(bar.getOpen());
			higherBars.add(higherBar);
		}

		if (bar.getHigh() > higherBar.getHigh()) {
			higherBar.setHigh(bar.getHigh());
		}
		if (bar.getLow() < higherBar.getLow()) {
			higherBar.setLow(bar.getLow());
		}
		higherBar.setClose(bar.getClose());
		higherBar.setDate(bar.getDate());
		higherBar.setVolume(bar.getVolume() + higherBar.getVolume());
	}

	public List<Bar> merge(final List<Bar> bars) {
		if ((null == bars) || bars.isEmpty()) {
			return bars;
		}
		final Calendar calendar = Calendar.getInstance();
		final List<Bar> higherBars = new ArrayList<Bar>();
		int value = -1;
		Bar higherBar = null;

		for (final Bar bar : bars) {
			calendar.setTime(bar.getDate());
			if (value != calendar.get(getUnit())) {
				higherBar = new Bar();
				higherBar.setOpen(bar.getOpen());
				higherBar.setHigh(Double.MIN_VALUE);
				higherBar.setLow(Double.MAX_VALUE);
				higherBars.add(higherBar);
				value = calendar.get(getUnit());
			}

			if (bar.getHigh() > higherBar.getHigh()) {
				higherBar.setHigh(bar.getHigh());
			}
			if (bar.getLow() < higherBar.getLow()) {
				higherBar.setLow(bar.getLow());
			}
			higherBar.setClose(bar.getClose());
			higherBar.setDate(bar.getDate());
			higherBar.setVolume(bar.getVolume() + higherBar.getVolume());
		}

		// There is no way to find out if last bar was fully formed with available data, thus discard it
		if (!higherBars.isEmpty()) {
			higherBars.remove(higherBars.size() - 1);
		}
		return higherBars;
	}

	public BarSpan up() {
		if (ordinal() == (values().length - 1)) {
			return null;
		} else {
			return values()[ordinal() + 1];
		}
	}

	public BarSpan down() {
		if (ordinal() == 0) {
			return null;
		} else {
			return values()[ordinal() - 1];
		}
	}

	// TODO needs re-write, does not take care of anything below DAILY
	public List<Bar> up(final List<Bar> bars) {
		if ((null == bars) || bars.isEmpty()) {
			return bars;
		}
		final BarSpan higher = up();

		if (null == higher) {
			return null;
		}
		final Calendar calendar = Calendar.getInstance();
		final List<Bar> higherBars = new ArrayList<Bar>();
		int value = -1;
		Bar higherBar = null;
		for (final Bar bar : bars) {
			calendar.setTime(bar.getDate());
			if (value != calendar.get(higher.getUnit())) {
				higherBar = new Bar();
				higherBar.setOpen(bar.getOpen());
				higherBars.add(higherBar);
				value = calendar.get(higher.getUnit());
			}

			if (bar.getHigh() > higherBar.getHigh()) {
				higherBar.setHigh(bar.getHigh());
			}
			if (bar.getLow() < higherBar.getLow()) {
				higherBar.setLow(bar.getLow());
			}
			higherBar.setClose(bar.getClose());
			higherBar.setDate(bar.getDate());
			higherBar.setVolume(bar.getVolume() + higherBar.getVolume());
		}

		return higherBars;
	}

	public int getCount() {
		return count;
	}

	public int getUnit() {
		return unit;
	}

	@Override
	public String toString() {
		return MessageSource.get(key);
	}


	public Date floor(final Date date) {
		synchronized (calendar) {
			calendar.setTime(date);
			return floor(calendar).getTime();
		}
	}

	public Calendar floor(final Calendar calendar) {
		switch (this) {
			case MONTHLY:
			case WEEKLY:
			case DAILY:
				// case HOURLY :
				calendar.set(Calendar.HOUR, 0);
				calendar.set(Calendar.HOUR_OF_DAY, 0);
				// case HALF_HOURLY :
				// case MINURELY_15 :
				// case MINUTELY_5 :
				// case MINUTELY_1 :
				calendar.set(Calendar.MINUTE, 0);
				// case TICK :
				calendar.set(Calendar.SECOND, 0);
				calendar.set(Calendar.MILLISECOND, 0);
		}
		return calendar;
	}

	public Calendar ceil(final Calendar calendar) {
		floor(calendar);
		add(calendar, 1);
		return calendar;
	}


	public Date previous() {
		return floor(Calendar.getInstance()).getTime();
	}

	public Date next() {
		return ceil(Calendar.getInstance()).getTime();
	}


}
